package com.scau.phr.business.auth.service;

import com.scau.phr.business.auth.anno.Permitting;
import com.scau.phr.business.auth.domain.Role;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 用于管理API的权限接口
 * @author yuzhiyi
 * @date 2018/7/29 17:48
 */
@Component
public class PowerManager implements ApplicationContextAware{

	private static HashMap<String,Set<Role>> apiRoles = new HashMap<>();
	private Logger logger = LoggerFactory.getLogger(PowerManager.class);

	/**
	 * 判断当前的Role是否满足当前url的条件
	 */
	public static boolean matchRole(String url,Role role){
		Set<Role> roles = apiRoles.get(url);
		if (roles == null || roles.contains(Role.ALL)){
			return true;
		}
		return roles.contains(role);
	}

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		Map<String,Object> controllerMap = applicationContext.getBeansWithAnnotation(RestController.class);
		controllerMap.values().forEach(controller->{
			Role[] commonRole = getRoleFromController(controller);
			String baseUrl = getBaseUrl(controller);
			ReflectionUtils.doWithMethods(controller.getClass(),method -> {
				if (!isApiMethod(method)){
					return;
				}
				String url = getApiMapping(method);
				if (method.isAnnotationPresent(Permitting.class)){
					Role[] roles = method.getAnnotation(Permitting.class).value();
					setRole(baseUrl,url,commonRole,roles);
				}
			});
		});
	}

	private static final Class[] apiAnnos  = new Class[]{RequestMapping.class, PostMapping.class, GetMapping.class};
	private boolean isApiMethod(Method method){
		for (Class anno : apiAnnos){
			if (method.isAnnotationPresent(anno)){
				return true;
			}
		}
		return false;
	}

	private String getApiMapping(Method method){
		if (method.isAnnotationPresent(RequestMapping.class)){
			return method.getAnnotation(RequestMapping.class).value()[0];
		}
		else if (method.isAnnotationPresent(GetMapping.class)){
			return method.getAnnotation(GetMapping.class).value()[0];
		}
		else if (method.isAnnotationPresent(PostMapping.class)){
			return method.getAnnotation(PostMapping.class).value()[0];
		}
		throw new IllegalArgumentException();
	}

	private static final Role[] NONE_ROLE = new Role[]{};
	private Role[] getRoleFromController(Object controller){
		if (controller.getClass().isAnnotationPresent(Permitting.class)){
			return controller.getClass().getAnnotation(Permitting.class).value();
		}
		return NONE_ROLE;
	}

	private String getBaseUrl(Object controller){
		if (controller.getClass().isAnnotationPresent(RequestMapping.class)){
			String  url = controller.getClass().getAnnotation(RequestMapping.class).value()[0];
			if (!url.startsWith("/")){
				url = "/" + url;
			}
			if (url.endsWith("/")){
				url = url.substring(0,url.length()-1);
			}
			return url;
		}
		return "/";
	}

	private void setRole(String baseUrl,String url,Role[] baseRole,Role[] roles){

		Set<Role> targetRoles = Stream.concat(Stream.of(baseRole),Stream.of(roles))
						.collect(Collectors.toSet());
		if (baseUrl.endsWith("/") && url.startsWith("/")){
			url = url.substring(0,url.length()-1);
		}else if (!baseUrl.endsWith("/") && !url.startsWith("/")){
			url = "/"+url;
		}
		url = baseUrl + url;
		logger.info("{}, roles:{}",url,targetRoles);
		apiRoles.put(url,targetRoles);
	}

}
